#ifndef __SHARED_MEMORY_HPP__
#define __SHARED_MEMORY_HPP__

#include <sys/shm.h>
#include "exception.hpp"

namespace ipc {

template <typename T>
class shared_memory {
	public:
		enum ERROR { NONE = 0, SHMGET = -2, SHMAT = -3, SHMCTL = -4 };

	protected:
		int id;
		key_t key;
		T* data;

		// Libera la memoria asignada al puntero.
		int dettach() {
			return shmdt ( reinterpret_cast<void *>(this->data) );
		}

		// asignación del bloque de memoria al espacio de direcciones del proceso
		int attach() {
			int error = NONE;
			void* temp = shmat ( this->id,NULL,0 );

			if ( temp == (void*)-1 )
				error = SHMAT;
			else
				this->data = reinterpret_cast<T*>(temp);
			return error;
		}

	public:
		shared_memory(const key_t& key) {
			this->key = key;
			this->id = -1;
			this->data = NULL;
		}

		~shared_memory() {}

		int getId() const {
			return this->id;
		}

		int getKey() const {
			return this->key;
		}

		operator bool () const {
			return (this->id > 0);
		}

		bool is_null() const {
			return (this->data == NULL);
		}

		int getState (struct shmid_ds& state) const {
			return (shmctl(this->id, IPC_STAT, &state) == 0) ? NONE : SHMCTL;
		}

		int get (int mode, size_t n = 1) {
			int error = NONE;
			// creacion de la memoria compartida
			this->id = shmget (this->key,sizeof(T)*n, mode);

			error = (this->id != -1) ? attach() : SHMGET;

			return error;
		}

		// Elimina el objeto IPC. No controla que ya este libre para ser destruida.
		int destroy() {
			// destruccion de la shared memory
			shmctl ( this->id,IPC_RMID,NULL );
			this->id = -1;
			this->data = NULL;
			return errno;
		}

		int free(bool auto_destroy = true) {
			int error = this->dettach();

			if (error == 0) {
				// consulta del estado de la memoria compartida, para saber si tiene que eliminar el objeto IPC.
				shmid_ds state;
				error = this->getState(state);
			
				if ( (auto_destroy) && (error == 0) && (state.shm_nattch == 0 ) )
					error = this->destroy();
			}
			return error;
		}

		const T& operator[] (size_t index) const {
			return (this->data[index]);
		}

		T& operator[] (size_t index) {
			return (this->data[index]);
		}

		const T& operator*() const {
			return *(this->data);
		}
		T& operator*() {
			return *(this->data);
		}

		T* operator->() {
			return (this->data);
		}

		const T* operator->() const {
			return (this->data);
		}

	static const shared_memory& null() {
		static shared_memory aux;
		aux.id = 0;
		aux.data = NULL;
		return aux;
	};

	bool operator== (const shared_memory& other) {
		bool iguales = false;
		if (this->id == other.id) {
			if (this->id != 0)
				iguales = (this->data == other.data);
		}
		return iguales;
	};

};

class shared_memory_exception : public exception {
	public:
		shared_memory_exception (int shmId, key_t key, const std::string& message) : exception(shmId,key,message) {}

		virtual std::string getClass() const {
			return "Shared memory";
		}
}; // shared_memory_exception

template <typename T>
class shared_memory_with_exceptions {
	protected:
		shared_memory<T> ptr;
	public:
		shared_memory_with_exceptions (const key_t& key) : ptr(key) {
		}
		
		inline int get (int mode, size_t n = 1) throw(shared_memory_exception) {
			if (this->ptr.get(mode,n) != 0 )
				throw shared_memory_exception(ptr.getId(),ptr.getKey(),"get");
			return 0;
		}

		key_t getKey() const {
			return ptr.getKey();
		}

		int getId() const {
			return ptr.getId();
		}

		inline int free (bool auto_destroy = true) throw(shared_memory_exception) {
			if (this->ptr.free(auto_destroy) != 0 )
				throw shared_memory_exception(ptr.getId(),ptr.getKey(),"free");
			return 0;
		}

		inline int destroy() throw(shared_memory_exception) {
			if ( this->ptr.destroy() != 0)
				throw shared_memory_exception(ptr.getId(),ptr.getKey(),"destroy");
			return 0;
		}

		operator bool() const {
			return *(this->ptr);
		}

		const T& operator[] (size_t index) const {
			return this->ptr[index];
		}

		T& operator[] (size_t index) {
			return this->ptr[index];
		}

		const T& operator*() const {
			return *(this->ptr);
		}
		T& operator*() {
			return *(this->ptr);
		}

		T* operator->() {
			return this->ptr.operator->();
		}

		const T* operator->() const {
			return this->ptr.operator->();
		}

}; // class shared_memory_with_exceptions

} // namespace ipc

#endif

